<html>
  <head>
    <title>应用程序服务</title>
  </head>
  <body>
    <script>
      // 应用程序服务 公约和最佳实现

            /*
                一、常规应用服务划分

                          为每个聚合根创建一个应用服务
            */
            /*
                二、应用服务接口

                        ①、为应用服务契约层中的每个应用程序服务定义一个接口。

                        ②、从IApplicationService 接口派生应用服务接口

                        ③、请使用AppService后缀作为接口名称（例如：IProductAppService）。

                        ③、为服务的输入和输出创建DTO（数据传输对象）。

                        ⑤、应用服务的方法不要返回实体。

                        ⑥、根据DTO最佳实践定义DTO。



                        1、输出

                                            避免为相同或相关实体定义太多输出DTO。相反，为实体定义一个基本和一个详细DTO。

                                      
                                            (1)、基本Dto  Basic DTO (作为其他Dto的基类，抽象的存在)

                                                      为聚合根定义基本DTO。

                                                      ①、直接在聚合根上包含所有原始属性。

                                                            例外：出于安全原因（如User.Password），可以排除属性。

                                                      ②、包括实体的所有子集合，其中集合中的每个项目都是一个简单的关系DTO。

                                                      ③、从聚合根的可扩展实体DTO类之一继承（实体实现IHasExtraProperties）。

                                                            eg：

                                                                [Serializable]
                                                                public class IssueDto : ExtensibleFullAuditedEntityDto<Guid>
                                                                {
                                                                    public string Title { get; set; }
                                                                    public string Text { get; set; }
                                                                    public Guid? MilestoneId { get; set; }
                                                                    public Collection<IssueLabelDto> Labels { get; set; }  // 包含实体的子集合
                                                                }

                                                                [Serializable]
                                                                public class IssueLabelDto
                                                                {
                                                                    public Guid IssueId { get; set; }
                                                                    public Guid LabelId { get; set; }
                                                                }

                                              
                                            (2)、详细DTO  Detailed DTO

                                                          如果实体引用其他聚合根，请为实体定义详细DTO。

                                                          ①、直接在实体上包含所有原始属性。

                                                                      出于安全原因（如User.Password），可以排除属性。

                                                                      排除通过ID引用的属性(如上面示例中的MilestoneId),为其添加导航属性。

                                                          ②、为每个引用属性包含一个基本DTO属性。

                                                          ③、包括实体的所有子集合，其中集合中的每个项目都是相关实体的 "基本DTO"。


                                                          eg:

                                                                    [Serializable]
                                                                    public class IssueWithDetailsDto : ExtensibleFullAuditedEntityDto<Guid>
                                                                    {
                                                                        public string Title { get; set; }
                                                                        public string Text { get; set; }
                                                                        public MilestoneDto Milestone { get; set; }
                                                                        public Collection<LabelDto> Labels { get; set; }
                                                                    }

                                                                    [Serializable]
                                                                    public class MilestoneDto : ExtensibleEntityDto<Guid>
                                                                    {
                                                                        public string Name { get; set; }
                                                                        public bool IsClosed { get; set; }
                                                                    }

                                                                    [Serializable]
                                                                    public class LabelDto : ExtensibleEntityDto<Guid>
                                                                    {
                                                                        public string Name { get; set; }
                                                                        public string Color { get; set; }
                                                                    }    

                     2、输出

                                不要在输入DTO中定义应用服务类中未使用的任何属性。
                                
                                不要在应用程序服务方法之间共享输入DTO。

                                不要从另一个输入DTO类继承输入DTO类。

                                         可以继承一个抽象的基本DTO类，  以这种方式在不同的DTO之间共享一些属性。 
                                         
                                         但是，在这种情况下应该非常小心，因为操作基本DTO会影响所有相关的DTO和服务方法。
                                         
                                         作为一种良好的做法，避免这样做。


                      3、方法   Methods

                                使用Asyncpostfix将服务方法定义为异步。

                                不要在方法名称中重复实体名称.

                                        示例：在IProductAppService中定义GetAsync(...)而不是GetProductAsync(...)。


                                (1)、获取单个实体的方法
                                                                      
                                          使用GetAsync方法名称。
                                          
                                          使用Id作为参数。

                                          返回Detailed Dto(详细Dto)

                                          eg：

                                                Task<QuestionWithDetailsDto> GetAsync(Guid id);

                                (2)、获取实体列表的方法

                                         ①、 使用GetListAsync方法名称。

                                         ②、 如果需要，请为过滤、排序和分页获取单个DTO参数：

                                                  尽可能实现可选的过滤器。

                                                  将排序和分页属性实现为可选并提供默认值。

                                                  限制最大页面大小（出于性能原因）。

                                         ③、返回详细DTO的列表。

                                                  eg： 
                                                      Task<List<QuestionWithDetailsDto>> GetListAsync(QuestionListQueryDto queryDto);

                                (3)、创建新实体的方法

                                              ①、使用CreateAsync方法名称。

                                              ②、获取专用输入DTO来创建实体。

                                              ③、从ExtensibleObject继承DTO类（或任何其他类实现IHasExtraProperties）以允许在需要时传递额外的属性。

                                              ④、为新创建的实体返回详细的DTO。
                                              
                                              ⑤、只需要最小信息来创建实体，但提供将其他信息设置为可选属性的可能性。


                                              eg：示例方法

                                                        Task<QuestionWithDetailsDto> CreateAsync(CreateQuestionDto questionDto);
                                                          
                                                          
                                              eg: 相关的输入Dto

                                                          [Serializable]
                                                          public class CreateQuestionDto : ExtensibleObject
                                                          {
                                                              [Required]
                                                              [StringLength(QuestionConsts.MaxTitleLength,
                                                                            MinimumLength = QuestionConsts.MinTitleLength)]
                                                              public string Title { get; set; }
                                                              
                                                              [StringLength(QuestionConsts.MaxTextLength)]
                                                              public string Text { get; set; } //Optional
                                                              
                                                              public Guid? CategoryId { get; set; } //Optional
                                                          }


                                 (4)、跟新实体方法：

                                              ①、使用UpdateAsync方法名称
                                              
                                              ②、获取专用输入DTO来更新实体。

                                              ③、从ExtensibleObject继承DTO类（或任何其他类实现IHasExtraProperties）以允许在需要时传递额外的属性。

                                              ④、获取实体的ID作为"分离的原始参数"。不要包含在更新DTO中。

                                              ⑤、用数据注释进行输入验证

                                                         尽可能在域之间共享常量（通过域共享包中定义的常量）。
                                                         
                                              ⑥、返回更新实体的详细DTO。


                                              eg：  

                                                    Task<QuestionWithDetailsDto> UpdateAsync(Guid id, UpdateQuestionDto updateQuestionDto);

                                        
                                 (5)、删除实体的方法：

                                              ①、使用DeleteAsync方法名称。

                                              ②、原始方法参数获取ID。


                                              eg:

                                                    Task DeleteAsync(Guid id);  
            */

            /*
                三、应用服务实施



                        1、

                                ①、开发完全独立于Web层的应用层。

                                ②、在应用层中实现应用服务接口。

                                            使用命名约定。例如：为IProductAppService接口创建ProductAppService类。

                                            继承自ApplicationService基类。

                                ③、使所有公共方法虚拟，以便开发人员可以继承和覆盖它们。

                                ④、不要使私有方法。相反，将它们设为受保护的虚拟，以便开发人员可以继承和覆盖它们。


                        2、使用仓储  Using Repositories

                                ①、请使用专门设计的存储库（如IProductRepository）。
                                
                                ②、不要使用通用存储库（如IRepository<Product>）。

                        3、数据查询  Querying Data

                                ①、不要在应用程序服务方法中使用LINQ/SQL从数据库中查询数据。

                                   存储库负责从数据源执行LINQ/SQL查询。


                        4、额外属性  Extra Properties

                                请使用MapExtraPropertiesTo扩展方法（参见）或
                                
                                配置对象映射器（MapExtraProperties）以允许应用程序开发人员能够扩展对象和服务。


                        5、删除/操作实体 
                        
                                总是从存储库中获取所有相关实体以对其执行操作。

                                在更新实体后调用存储库的Update/UpdateAsync方法。因为，并非所有数据库API都支持更改跟踪和自动更新。


                        6、操作文件

                                  不要在应用程序服务中使用任何Web组件，例如IFormFile或Stream。

                                  如果要提供文件，可以使用byte[]。

                                  使用Controller处理文件上传，然后将文件的byte[]传递给应用程序服务方法。



                        7、如何使用其他应用程序服务

                                  不要使用同一模块/应用程序的其他应用程序服务。相反

                                        使用域层执行所需的任务。

                                        提取一个新类并在应用程序服务之间共享，以在必要时完成代码重用。但是要小心不要把两个用例结合起来。

                                        它们一开始可能看起来很相似，但随着时间的推移可能会发展到不同的方向。所以，小心使用代码共享。

                                  只有在以下情况下才能使用他人的应用服务：

                                        它们是另一个模块/微服务的一部分。

                                        当前模块仅引用已使用模块的应用程序合约。



                                  

                                              
            */

            
    </script>
  </body>
</html>